/**
 * 
 */
package logic.trie;

import io.ImageReader;

import java.awt.image.BufferedImage;
import java.util.Vector;

import datamodel.RGB;
import datamodel.trie.Trie;
import datamodel.trie.TrieNode;

/**
 * provides methods to build and maintain a TrieCode- Tree
 * 
 * @author Jakob Karolus, Kevin Munk
 * @version 1.0
 * 
 */
public class TrieCode {

	private Trie trieCodeTree;

	/**
	 * constructor
	 * 
	 */
	public TrieCode() {
		this.trieCodeTree = new Trie();
	}
	
	private Vector<String> RGBtoStrings(RGB rgb){
		Vector<String> result = new Vector<String>();
		int a =  rgb.getBrightness();
		int r =  rgb.getRed();
		int g =  rgb.getGreen();
		int b =  rgb.getBlue();
		int[] codedBytes = new int[8];
		for(int i = 7; i > -1; i--){
			int bitmask = (int) Math.pow(2, i);
			int[] argb = {(b & bitmask), (g & bitmask),(r & bitmask),(a & bitmask)};
			for(int j = 0; j < 4; j++){
				if(i > j){
					argb[j] = (argb[j] >> i - j);
				} else {
					argb[j] = (argb[j] << j  - i);
				}
				codedBytes[i] = (codedBytes[i] | argb[j]);
			}	
			result.add(String.format("%4s",Integer.toBinaryString(codedBytes[i])).replace(" ", "0"));
		}
		return result;
	}
	
	private boolean isValidSetOfGroups(Vector<String> groups){
		if(groups.size() != 8){
			return false;
		}
		for(String group : groups){
			if(!Trie.isValidGroup(group)){
				return false;
			}
		}
		return true;
	}
	
	private RGB stringsToRGB(Vector<String> groups){
		if(!this.isValidSetOfGroups(groups)){
			throw new IllegalArgumentException("Vector<String> is not a valid RGB");
		}
		String[] argbString = {"","","",""};
		for(String group : groups){
			for(int i = 0; i < 4; i++){
				argbString[i] = argbString[i] + group.charAt(i);
			}
		}
		int[] argb = {0,0,0,0};
		for (int i = 0; i < 4; i++){
			argb[i] = Integer.valueOf(argbString[i], 2);
		}
		return new RGB(argb[1], argb[2], argb[3], argb[0]);
	}

	/**
	 * builds a trie from the given image
	 * 
	 * @param reader
	 *            the image via an ImageReader
	 */
	public void buildTrie(ImageReader reader) {
		this.trieCodeTree.clear();
		this.trieCodeTree = new Trie();
		for(RGB rgb : reader){
			this.addColor(rgb);
		}
	}

	/**
	 * encrypts the given picture (via an ImageReader) using the stored trie
	 * 
	 * @param reader the picture via an ImageReader
	 * @return the encrypted picture as a binary string
	 */
	public String encryptImage(ImageReader reader) {
		this.trieCodeTree.moveToRoot();
		StringBuilder returnString = new StringBuilder("");
		for(RGB rgb : reader){
			Vector<String> rgbCode = this.RGBtoStrings(rgb);
			boolean childInTreeFlag = true;
			for(String code : rgbCode){
				if(childInTreeFlag && (this.trieCodeTree.hasChildForGroup(code))){
					returnString.append(code);
					this.trieCodeTree.moveToChild(code);
				} else {
					returnString.append("1000");
					childInTreeFlag = false;
				}	
			}
			this.trieCodeTree.moveToRoot();
		}
		return returnString.toString();
	}

	
	/**
	 * adds the color to this trie<br>
	 * 
	 * @param color
	 *            the color to add
	 */
	public void addColor(RGB color) {
		this.trieCodeTree.moveToRoot();
		for(String code : this.RGBtoStrings(color)){
			
			if(!this.trieCodeTree.hasChildForGroup(code)){
				this.trieCodeTree.setChildForGroup(new TrieNode(this.trieCodeTree.getCurrentNode()), code);
			}
			
			this.trieCodeTree.moveToChild(code);
		}
	}


	/**
	 * decrypts the given picture and returns it via a BufferedImage
	 * 
	 * @param picture
	 *            the picture encrypted as a binary string
	 * @param height
	 *            the height of the picture
	 * @param width
	 *            the width of the picture
	 * @return BufferedImage containing the picture
	 */
	public BufferedImage decryptImage(String picture, int height, int width) {
		BufferedImage image = new BufferedImage(width, height,
				BufferedImage.TYPE_INT_ARGB);
		// prepare counters
		int x = 0;
		int y = 0;
		int rgbCounter = 0;
		// prepare string vector for RGB conversion
		Vector<String> rgbStrings = new Vector<String>();
		// begin run through of the picture string
		for(int pos = 0; pos < picture.length(); pos += 4){	
			// add the latest bit string to the string vector
			rgbStrings.add(picture.substring(pos, pos+4));
			// increment the counter, modulo 8 (in order to create groups of 8 bit strings)
			rgbCounter = (rgbCounter + 1) % 8;
			if((rgbCounter == 0) && !rgbStrings.isEmpty()){ 
				// if the counter has gone back to zero and there are strings to process
				if(x == width){
					if(y == height-1) // (cannot increment) finished
						break;
					else {
						y++;
						x=0;
					}
				}
				// Add the newest RGB to the image
				image.setRGB(x, y, this.stringsToRGB(rgbStrings).getRGBValue());
				// and prepare the string vector for new input
				rgbStrings.clear();
				++x;
			}
		}
		return image;
	}

	/**
	 * severs the given amount of levels in the stored tree to compress image
	 * information<br>
	 * if the amount is bigger than the actual number of levels nothing is done
	 * 
	 * @param count
	 *            the amount of levels to sever
	 * @return true if severing was successful; otherwise false (count >= number
	 *         of levels)
	 */
	public boolean compress(int count) {
		if(count >= 8){
			return false;
		}
		this.trieCodeTree.moveToRoot();
		compressAcc(8-count);
		return true;
	}
	
	private void compressAcc(int count){
		Trie tree = this.trieCodeTree;
		if (count == 0){
			for(String code :Trie.TRIE_CODES){
				tree.getCurrentNode().setChildForGroup(null, code);
			}
			return;
		}
		for(String code : Trie.TRIE_CODES){
			if(tree.hasChildForGroup(code)){
				tree.moveToChild(code);
				compressAcc(count - 1);
				tree.moveToParent();
			}
		}
	}
	/**
	 * return true if the given color is in the trie
	 * 
	 * @param color
	 *            the color to search
	 * @return true if tree contains color; otherwise false
	 */
	public boolean containsColor(RGB color) {
		this.trieCodeTree.moveToRoot();
		Vector<String> rgbCode = this.RGBtoStrings(color);
		for(String code : rgbCode){
			if(!this.trieCodeTree.hasChildForGroup(code)){
				return false;
			} else {
				this.trieCodeTree.moveToChild(code);
			}
		}
		return true;
	}

	/**
	 * @return the trieCodeTree
	 */
	public Trie getTrieCodeTree() {
		return trieCodeTree;
	}
}